// renderer.hpp
//

#ifndef _RENDERER_HPP
#define _RENDERER_HPP

#include "base.hpp"
#include "node.hpp"
#include "pipeline.hpp"
#include "sceneview.hpp"

class RenderEngine;
class Renderer;



// RenderContext
//
class RenderContext
{
  friend Renderer;

  static constexpr GLsizeiptr UBO_SLOT_SIZE_INT    = 4;
  static constexpr GLsizeiptr UBO_SLOT_SIZE_FLOAT  = 4;
  static constexpr GLsizeiptr UBO_SLOT_SIZE_VEC3   = 12;
  static constexpr GLsizeiptr UBO_SLOT_SIZE_VEC4   = 16;
  static constexpr GLsizeiptr UBO_SLOT_SIZE_MAT3x4 = 48; // [FIXME] not sure
  static constexpr GLsizeiptr UBO_SLOT_SIZE_MAT4   = 64;
    
  struct UBO
  {
    GLuint buffer;
    GLuint binding;
  };

  struct UBOComponent
  {
    GLintptr offset;

    void set ( /* [fixme] */ UBO &ubo,
               GLsizeiptr size,
               const void *data )
    {
      gl.BindBuffer(GL_UNIFORM_BUFFER, ubo.buffer);
      gl.BufferSubData(GL_UNIFORM_BUFFER, offset, size, data);
      gl.BindBuffer(GL_UNIFORM_BUFFER, 0); // [FIXME] useless?
    }
    
    void set_int ( /* [fixme] */ UBO &ubo,
                   int32_t i )
    {
      set(ubo, UBO_SLOT_SIZE_INT, &i);
    }
    
    void set_float ( /* [fixme] */ UBO &ubo,
                     float f )
    {
      set(ubo, UBO_SLOT_SIZE_FLOAT, &f);
    }
    
    void set_vec3 ( /* [fixme] */ UBO &ubo,
                    const glm::vec3 &v )
    {
      set(ubo, UBO_SLOT_SIZE_VEC3, glm::value_ptr(v));
    }
    
    void set_mat3 ( /* [fixme] */ UBO &ubo,
                    const glm::mat3 &m )
    {
      // [FIXME]not sure the conversion is necessary
      set(ubo, UBO_SLOT_SIZE_MAT3x4, glm::value_ptr(glm::mat3x4{m}));
    }
    
    void set_mat4 ( /* [fixme] */ UBO &ubo,
                    const glm::mat4 &m )
    {
      set(ubo, UBO_SLOT_SIZE_MAT4, glm::value_ptr(m));
    }
  };

  struct UBOMatrices : UBO
  {
    UBOComponent projection;
    UBOComponent model;
    UBOComponent normal;
  };

  struct UBOLight
  {
    UBOComponent type;
    UBOComponent pos;
    UBOComponent ambient;
    UBOComponent diffuse;
    UBOComponent specular;
  };

  
  struct UBOLighting : UBO
  {
    UBOComponent view_pos;
    UBOLight lights[MAX_LIGHTS];
  };

  struct UBOMaterial : UBO
  {
    UBOComponent ambient;
    UBOComponent diffuse;
    UBOComponent specular;
    UBOComponent shininess;
  };
  
public:
  RenderContext ( Renderer &renderer );

  // [TEMP]
  bool is_textured () { return pipeline && pipeline->is_textured(); }

  void set_projection ( const glm::mat4 &m );
  void set_view_pos ( const glm::vec3 &p );
  void set_model_matrix ( const glm::mat4 &m );
  void set_light ( const wgl::Light &light );
  void set_material ( const wgl::Material *material );
  
  void set_render_mode ( RenderModeID mode ) { GLT_ASSERT(mode >= 0); render_mode = mode; }
  void set_render_group ( EntityGroupID group );
  
private:
  Renderer &renderer;

  std::unordered_map<std::string, GLuint> ubos;

  static constexpr GLuint UBO_MATRICES_BINDING = 0;
  static constexpr const char *UBO_MATRICES_NAME = "Matrices";

  static constexpr GLuint UBO_LIGHTING_BINDING = 1;
  static constexpr const char *UBO_LIGHTING_NAME = "Lighting";

  static constexpr GLuint UBO_MATERIAL_BINDING = 2;
  static constexpr const char *UBO_MATERIAL_NAME = "Material";
  
  UBOMatrices ubo_matrices{};
  UBOLighting ubo_lighting{};
  UBOMaterial ubo_material{};
  
  RenderModeID render_mode{0};
  EntityGroupID current_group{-1};
  Pipeline *pipeline{nullptr};

  int n_lights{0};
  
  void render_init ();  
  void render_begin ();
  void render_end ();

  void ubo_init ( UBO &ubo,
                  const char *name,
                  GLuint binding );
  void ubo_component_init ( UBOComponent &component,
                            const std::string &name );

  void ubo_matrices_init ();
  void ubo_lighting_init ();
  void ubo_material_init ();
};



// Renderer
//
class Renderer
{
public:
  Renderer ( RenderEngine &engine,
             Node &scene );

  RenderEngine &get_engine () { return engine; }

  void set_render_mode ( RenderModeID mode ) { render_context.set_render_mode(mode); }

  void render_prepare ();
  void render_init ();
  void render ();


private:
  RenderEngine &engine;
  Node &scene;
  
  SceneView scene_view;  
  RenderContext render_context;

  void prepare_node ( Node &node );
  void render_node ( Node &node );
};



#endif
